/**
 * @file src/nvhttp.h
 * @brief Declarations for the nvhttp (GameStream) server.
 */
// macros
#pragma once

// standard includes
#include <string>

// lib includes
#include <boost/property_tree/ptree.hpp>
#include <Simple-Web-Server/server_https.hpp>

// local includes
#include "crypto.h"
#include "thread_safe.h"

namespace http {
    extern std::string unique_id;
}
/**
 * @brief Contains all the functions and variables related to the nvhttp (GameStream) server.
 */
namespace nvhttp {

    /**
     * @brief The protocol version.
     * @details The version of the GameStream protocol we are mocking.
     * @note The negative 4th number indicates to Moonlight that this is Sunshine.
     */
    constexpr auto VERSION = "7.1.431.-1";

    /**
     * @brief The GFE version we are replicating.
     */
    constexpr auto GFE_VERSION = "3.23.0.74";

    /**
     * @brief The HTTP port, as a difference from the config port.
     */
    constexpr auto PORT_HTTP = 0;

    /**
     * @brief The HTTPS port, as a difference from the config port.
     */
    constexpr auto PORT_HTTPS = -5;

    /**
     * @brief Start the nvhttp server.
     * @examples
     * nvhttp::start();
     * @examples_end
     */
    void start();

    /**
     * @brief Setup the nvhttp server.
     * @param pkey
     * @param cert
     */
    void setup(const std::string &pkey, const std::string &cert);

    class SunshineHTTPS: public SimpleWeb::HTTPS {
    public:
        SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
                SimpleWeb::HTTPS(io_context, ctx) {
        }

        virtual ~SunshineHTTPS() {
            // Gracefully shutdown the TLS connection
            SimpleWeb::error_code ec;
            shutdown(ec);
        }
    };

    enum class PAIR_PHASE {
        NONE,  ///< Sunshine is not in a pairing phase
        GETSERVERCERT,  ///< Sunshine is in the get server certificate phase
        CLIENTCHALLENGE,  ///< Sunshine is in the client challenge phase
        SERVERCHALLENGERESP,  ///< Sunshine is in the server challenge response phase
        CLIENTPAIRINGSECRET  ///< Sunshine is in the client pairing secret phase
    };

    struct pair_session_t {
        struct {
            std::string uniqueID = {};
            std::string cert = {};
            std::string name = {};
        } client;

        std::unique_ptr<crypto::aes_t> cipher_key = {};
        std::vector<uint8_t> clienthash = {};

        std::string serversecret = {};
        std::string serverchallenge = {};

        struct {
            util::Either<
                    std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>,
                    std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>>
                    response;
            std::string salt = {};
        } async_insert_pin;

        /**
         * @brief used as a security measure to prevent out of order calls
         */
        PAIR_PHASE last_phase = PAIR_PHASE::NONE;
    };

    /**
     * @brief removes the temporary pairing session
     * @param sess
     */
    void remove_session(const pair_session_t &sess);

    /**
     * @brief Pair, phase 1
     *
     * Moonlight will send a salt and client certificate, we'll also need the user provided pin.
     *
     * PIN and SALT will be used to derive a shared AES key that needs to be stored
     * in order to be used to decrypt_symmetric in the next phases.
     *
     * At this stage we only have to send back our public certificate.
     */
    void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);

    /**
     * @brief Pair, phase 2
     *
     * Using the AES key that we generated in phase 1 we have to decrypt the client challenge,
     *
     * We generate a SHA256 hash with the following:
     *  - Decrypted challenge
     *  - Server certificate signature
     *  - Server secret: a randomly generated secret
     *
     * The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
     */
    void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);

    /**
     * @brief Pair, phase 3
     *
     * Moonlight will send back a `serverchallengeresp`: an AES encrypted client hash,
     * we have to send back the `pairingsecret`:
     * using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
     */
    void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);

    /**
     * @brief Pair, phase 4 (final)
     *
     * We now have to use everything we exchanged before in order to verify and finally pair the clients
     *
     * We'll check the client_hash obtained at phase 3, it should contain the following:
     *   - The original server_challenge
     *   - The signature of the X509 client_cert
     *   - The unencrypted client_pairing_secret
     * We'll check that SHA256(server_challenge + client_public_cert_signature + client_secret) == client_hash
     *
     * Then using the client certificate public key we should be able to verify that
     * the client secret has been signed by Moonlight
     */
    void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);

    /**
     * @brief Compare the user supplied pin to the Moonlight pin.
     * @param pin The user supplied pin.
     * @param name The user supplied name.
     * @return `true` if the pin is correct, `false` otherwise.
     * @examples
     * bool pin_status = nvhttp::pin("1234", "laptop");
     * @examples_end
     */
    bool pin(std::string pin, std::string name);

    /**
     * @brief Remove single client.
     * @examples
     * nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
     * @examples_end
     */
    int unpair_client(std::string uniqueid);

    /**
     * @brief Get all paired clients.
     * @return The list of all paired clients.
     * @examples
     * boost::property_tree::ptree clients = nvhttp::get_all_clients();
     * @examples_end
     */
    boost::property_tree::ptree get_all_clients();

    /**
     * @brief Remove all paired clients.
     * @examples
     * nvhttp::erase_all_clients();
     * @examples_end
     */
    void erase_all_clients();
}  // namespace nvhttp